Pneumonia Front (May 16, 2023)#

What is a Pneumonia Front?#

This is a term, originally coined by meteorologists in Milwaukee, Wisconsin, that refers to a strong cold front around the Great Lakes that leads to temperatures cooling very quickly, at a rate of 16 degrees Fahrenheit in 1 hour. These fronts can be quite common in the spring around Milwaukee and Chicagoland. We observed one of these fronts on May 16, 2023, observing this steep temperature decrease at one of our sensors at Northeastern Illinois University on the north side of Chicago, Illinois!

The Code to Create the Plots#

Hide code cell source
import sage_data_client
from bokeh.models.formatters import DatetimeTickFormatter
import hvplot.pandas
import hvplot.xarray
import pytz
import holoviews as hv
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import xarray as xr
import matplotlib.pyplot as plt
from metpy.plots import USCOUNTIES
import metpy.calc as mpcalc
import act
import numpy as np
import pandas as pd
import warnings
from bokeh.models import DatetimeTickFormatter
import panel as pn

start="2023-05-16T22:00:00Z"
end="2023-05-17T00:00:00Z"

def apply_formatter(plot, element):
    plot.handles['xaxis'].formatter = DatetimeTickFormatter(hours='%m/%d/%Y \n %H:%M',
                                                            minutes='%m/%d/%Y \n %H:%M',
                                                            hourmin='%m/%d/%Y \n %H:%M',
                                                            days='%m/%d/%Y \n %H:%M',
                                                            months='%m/%d/%Y \n %H:%M')
xr.set_options(keep_attrs=True)
warnings.filterwarnings("ignore")
hv.extension("bokeh")



# Dictionary for renaming to standard names
variable_rename_dict = {'wxt.env.humidity':'relative_humidity',
                        'wxt.env.pressure':'air_pressure',
                        'wxt.env.temp':'air_temperature',
                        'wxt.heater.temp':'heater_temperature',
                        'wxt.heater.volt':'heater_voltage',
                        'wxt.rain.accumulation':'rain_accumulation',
                        'wxt.wind.direction':'wind_direction',
                        'wxt.wind.speed':'wind_speed',
                        'sys.gps.lat':'latitude',
                        'sys.gps.lon':'longitude',
                    }

# Dictionary for units that are missing
units_dict = {'wxt.env.temp': 'degC',
              'wxt.env.pressure':'hPa',
              'wxt.env.humidity':'percent',
              'wxt.wind.speed':'m/s',
              'wxt.wind.direction':'degrees'}

def generate_data_array(df, variable, rename_variable_dict=variable_rename_dict):
    new_variable_name = rename_variable_dict[variable]
    df_variable= df.loc[df.name == variable]
    ds = df_variable.to_xarray().rename({'value':new_variable_name,
                                         'timestamp':'time',
                                         'meta.vsn':'node'})
    ds[new_variable_name].attrs['units'] = df_variable['meta.units'].values[0]
    ds['time'] = pd.to_datetime(ds.time)
    ds.attrs['datastream'] = ds.node.values[0]
    return ds[[new_variable_name]]

def generate_dataset(df, variables, rename_variable_dict=variable_rename_dict):
    try:
        reindexed = df.set_index(['meta.vsn', 'timestamp'])
    except:
        reindexed = df.set_index(['timestamp'])
    return xr.merge([generate_data_array(reindexed, variable) for variable in variables])


# Query and load for n numbder of days
wxt_df = sage_data_client.query(
    start=start,
    end=end,
    filter={
        "sensor": "vaisala-wxt536",
        "name": "wxt.env.*"
    }
)

wxt_df1 = sage_data_client.query(
    start=start,
    end=end,
    filter={
        "sensor": "vaisala-wxt536",
        "name": "wxt.wind.*"
    }
)

# Use local Chicago times
wxt_df = pd.concat([wxt_df, wxt_df1])
#time = pd.to_datetime(wxt_df.timestamp.values, utc=True)
#wxt_df['timestamp'] = time.tz_convert(pytz.timezone("America/Chicago"))

try:

    # Discover what variables we have and what to load into xarray
    wxt_variables = wxt_df.name.unique()
    wxt_df['meta.units'] = wxt_df.name.map(units_dict)
    
    wxt_ds = generate_dataset(wxt_df, wxt_variables).squeeze().metpy.parse_cf()
    wxt_ds['node'] = ['W08D at Northeastern Illinois University']
    # Convert to Local Chicago Time - THIS IS ONLY FOR DAYLIGHT TIME
    wxt_ds['time'] = wxt_ds.time - pd.Timedelta(hours=5)
    wxt_ds['air_dewpoint_temperature'] = mpcalc.dewpoint_from_relative_humidity(wxt_ds.air_temperature, wxt_ds.relative_humidity)
    
    # Convert to degrees Fahrenheit
    wxt_ds['air_temperature'] = wxt_ds['air_temperature'].metpy.quantify().metpy.convert_units('degF').metpy.dequantify()
    wxt_ds['air_dewpoint_temperature'] = wxt_ds['air_dewpoint_temperature'].metpy.quantify().metpy.convert_units('degF').metpy.dequantify()
    wxt_ds['wind_speed'] = wxt_ds['wind_speed'].metpy.quantify().metpy.convert_units('mph').metpy.dequantify()
    
    # Resample to 1 minute freqency
    minute_ds = wxt_ds.resample(time='1T').mean()

    plots = []
    temp_plot = wxt_ds.air_temperature.hvplot(color='red',
                                              xlabel='Local Time (Chicago)',
                                              label='Air Temperature (degF)')
    dewp_plot = wxt_ds.air_dewpoint_temperature.hvplot(color='green',
                                                       xlabel='Local Time (Chicago)',
                                                       label='Dewpoint Temperature (degF)')
    plots.append((temp_plot * dewp_plot).opts(hooks=[apply_formatter]))
    
    meteogram_variables = ['wind_speed', 'wind_direction']
    for variable in meteogram_variables:
        plots.append((wxt_ds[variable].hvplot.line(label='10 Hz Data') * 
                     minute_ds[variable].hvplot.line(label='1 Minute Data')).opts(hooks=[apply_formatter]))
    
    
except:
    plots = pn.Row(title='No Data Available')
---------------------------------------------------------------------------
TimeoutError                              Traceback (most recent call last)
File ~/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/urllib/request.py:1348, in AbstractHTTPHandler.do_open(self, http_class, req, **http_conn_args)
   1347 try:
-> 1348     h.request(req.get_method(), req.selector, req.data, headers,
   1349               encode_chunked=req.has_header('Transfer-encoding'))
   1350 except OSError as err: # timeout error

File ~/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/http/client.py:1283, in HTTPConnection.request(self, method, url, body, headers, encode_chunked)
   1282 """Send a complete request to the server."""
-> 1283 self._send_request(method, url, body, headers, encode_chunked)

File ~/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/http/client.py:1329, in HTTPConnection._send_request(self, method, url, body, headers, encode_chunked)
   1328     body = _encode(body, 'body')
-> 1329 self.endheaders(body, encode_chunked=encode_chunked)

File ~/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/http/client.py:1278, in HTTPConnection.endheaders(self, message_body, encode_chunked)
   1277     raise CannotSendHeader()
-> 1278 self._send_output(message_body, encode_chunked=encode_chunked)

File ~/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/http/client.py:1038, in HTTPConnection._send_output(self, message_body, encode_chunked)
   1037 del self._buffer[:]
-> 1038 self.send(msg)
   1040 if message_body is not None:
   1041 
   1042     # create a consistent interface to message_body

File ~/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/http/client.py:976, in HTTPConnection.send(self, data)
    975 if self.auto_open:
--> 976     self.connect()
    977 else:

File ~/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/http/client.py:1448, in HTTPSConnection.connect(self)
   1446 "Connect to a host on a given (SSL) port."
-> 1448 super().connect()
   1450 if self._tunnel_host:

File ~/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/http/client.py:942, in HTTPConnection.connect(self)
    941 sys.audit("http.client.connect", self, self.host, self.port)
--> 942 self.sock = self._create_connection(
    943     (self.host,self.port), self.timeout, self.source_address)
    944 # Might fail in OSs that don't implement TCP_NODELAY

File ~/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/socket.py:857, in create_connection(address, timeout, source_address)
    856 try:
--> 857     raise err
    858 finally:
    859     # Break explicitly a reference cycle

File ~/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/socket.py:845, in create_connection(address, timeout, source_address)
    844     sock.bind(source_address)
--> 845 sock.connect(sa)
    846 # Break explicitly a reference cycle

TimeoutError: [Errno 110] Connection timed out

During handling of the above exception, another exception occurred:

URLError                                  Traceback (most recent call last)
Cell In[1], line 75
     71     return xr.merge([generate_data_array(reindexed, variable) for variable in variables])
     74 # Query and load for n numbder of days
---> 75 wxt_df = sage_data_client.query(
     76     start=start,
     77     end=end,
     78     filter={
     79         "sensor": "vaisala-wxt536",
     80         "name": "wxt.env.*"
     81     }
     82 )
     84 wxt_df1 = sage_data_client.query(
     85     start=start,
     86     end=end,
   (...)
     90     }
     91 )
     93 # Use local Chicago times

File ~/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/site-packages/sage_data_client/query.py:115, in query(start, end, head, tail, filter, endpoint, bucket, experimental_func, experimental_window)
    112 headers = {"Accept-Encoding": "gzip"}
    113 req = Request(endpoint, data, headers=headers)
--> 115 with urlopen(req) as f:
    116     content_encoding = f.headers.get("Content-Encoding", "")
    117     if "gzip" in content_encoding:

File ~/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/urllib/request.py:216, in urlopen(url, data, timeout, cafile, capath, cadefault, context)
    214 else:
    215     opener = _opener
--> 216 return opener.open(url, data, timeout)

File ~/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/urllib/request.py:519, in OpenerDirector.open(self, fullurl, data, timeout)
    516     req = meth(req)
    518 sys.audit('urllib.Request', req.full_url, req.data, req.headers, req.get_method())
--> 519 response = self._open(req, data)
    521 # post-process response
    522 meth_name = protocol+"_response"

File ~/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/urllib/request.py:536, in OpenerDirector._open(self, req, data)
    533     return result
    535 protocol = req.type
--> 536 result = self._call_chain(self.handle_open, protocol, protocol +
    537                           '_open', req)
    538 if result:
    539     return result

File ~/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/urllib/request.py:496, in OpenerDirector._call_chain(self, chain, kind, meth_name, *args)
    494 for handler in handlers:
    495     func = getattr(handler, meth_name)
--> 496     result = func(*args)
    497     if result is not None:
    498         return result

File ~/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/urllib/request.py:1391, in HTTPSHandler.https_open(self, req)
   1390 def https_open(self, req):
-> 1391     return self.do_open(http.client.HTTPSConnection, req,
   1392         context=self._context, check_hostname=self._check_hostname)

File ~/miniconda3/envs/instrument-cookbooks-dev/lib/python3.10/urllib/request.py:1351, in AbstractHTTPHandler.do_open(self, http_class, req, **http_conn_args)
   1348         h.request(req.get_method(), req.selector, req.data, headers,
   1349                   encode_chunked=req.has_header('Transfer-encoding'))
   1350     except OSError as err: # timeout error
-> 1351         raise URLError(err)
   1352     r = h.getresponse()
   1353 except:

URLError: <urlopen error [Errno 110] Connection timed out>

View the Data!#

Here, we are looking at the Temperature (red), Dewpoint (green), and Wind Speed and Direction (blue). Notice the temperature drop at around 5:30 PM local time, and the increase in wind speed!

Hide code cell source
display(hv.Layout(plots).opts(title='CROCUS Observations at Northeastern Illinois').cols(1))